import { ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, inject, DestroyRef, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OrgNode, Section } from './types';
import { Subscription, fromEvent } from 'rxjs';
import { PersonCardComponent } from './components/person-card/index';
import { findLastIndex } from 'lodash-es';
import { gsap } from "gsap";
import { ToastrService } from 'ngx-toastr';
import { getChromeVersion } from './util';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

const Duration = 0.5;
const Gap = 28;
const SecPaddingTop = 32;
const MaxColumnCount = 5;
const MidIndex = (MaxColumnCount + 1) / 2 - 1;
// const CardWidth = 194;
const CardHeight = 212;
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, PersonCardComponent],
  templateUrl: './app.html',
  styleUrl: './app.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None
})
export class AppComponent {

  readonly cdr = inject(ChangeDetectorRef);
  readonly toastr = inject(ToastrService);
  sections: Section[] = [];
  @ViewChild('orgWrapper') orgWrapperEl!: ElementRef<HTMLDivElement>;
  scrollSub!: Subscription;
  resizeSub!: Subscription;
  orgs: OrgNode[] = [];
  activeSection: OrgNode | null = null;
  hasMoreSectionOpened: Record<string, boolean> = {}

  readonly http = inject(HttpClient);

  constructor(destroyRef: DestroyRef) {
    destroyRef.onDestroy(() => {
      this.scrollSub?.unsubscribe();
      this.resizeSub?.unsubscribe();
    });
  }

  animating = false;

  ngOnInit() {
    this.http.get<OrgNode[]>(environment.apiUrl + '/assets/bleach.json').subscribe(res => {
      if (res[0]) {
        this.orgs = res;
        this.activeSection = res[0];
        this.init();
        this.cdr.markForCheck();
      }
    });
  }

  private init() {
    if (!this.activeSection) return;
    const subordinate = this.activeSection.children || [];
    this.sections[0] = {
      leader: this.activeSection,
      subordinate
    };
    this.initSectionOpened(this.sections[0]);
    this.updateSectionNode();
    this.scrollSub = fromEvent(this.orgWrapperEl.nativeElement, 'scrollend').subscribe((event) => {
      console.log('scroll end');
      this.updateScroll(event.target as HTMLDivElement);
    });
    const v = getChromeVersion();
    const method = v ? v >= 114 ? 'info' : 'warning' : 'warning';
    this.toastr[method]('当前：Chrome ' + v.toString(), '新语法较多, 需Chrome 114+', {
      timeOut: 20000,
      progressBar: true,
      tapToDismiss: false,
      closeButton: true,
      positionClass: 'toast-bottom-right',
    });
  }

  private updateSectionNode() {
    const orgs = this.orgWrapperEl.nativeElement?.getElementsByClassName('org-sec') as HTMLCollectionOf<HTMLDivElement>;
    if (this.sections.length && orgs.length) {
      const orgArr = Array.from(orgs);
      this.sections = this.sections.map(sec => {
        const el = orgArr.find(item => item.dataset['id'] === sec.leader.id);
        return el ? {
          ...sec,
          el,
          // top: el.offsetTop - CardHeight - SecPaddingTop - Gap
          top: Math.max(el.offsetTop - CardHeight - SecPaddingTop - Gap, 0)
        } : sec;
      });
    }
  }

  calcSecStyle({ leader, subordinate }: Section) {
    const opened = this.hasMoreSectionOpened[leader.id];
    const len = opened == null ? subordinate.length : opened ? subordinate.length : subordinate.slice(0, MaxColumnCount * 2 - 1).length;
    const columnCount = Math.min(MaxColumnCount, len);
    const rows = Math.ceil(len / columnCount);
    const height = Math.max((rows + 1) * (CardHeight + Gap), CardHeight * (rows + 1) + Gap * (rows - 1), 600);
    // console.log('height', height, rows);
    return {
      '--column-count': columnCount,
      '--person-wrap-height': height + 'px'
    }
  }

  isSticky(person: OrgNode, secIndex: number) {
    const nextSec = this.sections[secIndex + 1];
    // console.log('isSticky', person, nextSec)
    return nextSec && person && nextSec?.leader?.id === person?.id;
  }

  hasChildren(children: OrgNode[], secIndex: number) {
    const nextSec = this.sections[secIndex + 1];
    return nextSec && children.find(item => item.id === nextSec.leader.id);
  }

  selectPerson(person: OrgNode) {
    if (person.children?.length && !this.animating) {
      const secIndex = this.sections.findIndex(item => item.leader.id === person.id);
      this.activeSection = person;
      if (secIndex > -1) { // 直接选中leader
        this.jumpTo(secIndex);
      } else { // 选的是队员
        const subordinateIndex = this.sections.findIndex(item => item.subordinate.find(item => item.id === person.id));
        this.sections[subordinateIndex + 1] = {
          leader: person,
          subordinate: person.children
        };
        this.initSectionOpened(this.sections[subordinateIndex + 1]);
        const personIndex = this.sections[subordinateIndex].subordinate.findIndex(item => item.id === person.id);
        const shouldAnimation = personIndex > -1 && personIndex !== MidIndex && this.sections[subordinateIndex].subordinate[MidIndex];
        if (shouldAnimation) {
          this.animating = true;
          this.swap(this.sections[subordinateIndex], personIndex, (tl) => {
            this.animating = false;
            // 动画是交换，但下面这行实际是插入
            // this.sections[subordinateIndex].subordinate.splice(MidIndex, 0, this.sections[subordinateIndex].subordinate.splice(personIndex, 1)[0]);
            const arr = this.sections[subordinateIndex].subordinate.slice();
            [arr[MidIndex], arr[personIndex]] = [arr[personIndex], arr[MidIndex]];
            this.sections[subordinateIndex].subordinate = arr;
            tl.revert();
            this.cdr.markForCheck();
            setTimeout(() => {
              this.updateSectionNode();
              this.jumpTo(subordinateIndex + 1);
            }, 0);
          });
        } else {
          this.sections[subordinateIndex].subordinate.splice(MidIndex, 0, this.sections[subordinateIndex].subordinate.splice(personIndex, 1)[0]);
          setTimeout(() => {
            this.updateSectionNode();
            this.jumpTo(subordinateIndex + 1);
          }, 0);
        }
      }
    }
  }

  swap(section: Section, personIndex: number, onComplete: (tl: gsap.core.Timeline) => void) {
    const index = this.sections.findIndex(item => item.leader.id === section.leader.id);
    const orgEl = this.sections[index].el;
    if (orgEl) {
      const cardContainer = orgEl.querySelectorAll('.subordinates')[0];
      const personCard = cardContainer.children[personIndex] as HTMLElement;
      const midCard = cardContainer.children[MidIndex] as HTMLElement;

      if (personCard && midCard) {
        const personCardRect = personCard.getBoundingClientRect();
        const midCardRect = midCard.getBoundingClientRect();
        const tl = gsap.timeline({
          defaults: {
            duration: Duration,
            ease: "power1.inOut"
          },
          onComplete() {
            onComplete(tl);
          }
        });
        tl.to(personCard, {
          x: -(personCardRect.left - midCardRect.left),
          y: -(personCardRect.top - midCardRect.top),
        });
        tl.to(midCard, {
          x: (personCardRect.left - midCardRect.left),
          y: (personCardRect.top - midCardRect.top),
        }, 0);
      }
    }
  }

  private initSectionOpened(section: Section) {
    if (section.subordinate.length > MaxColumnCount * 2) {
      this.hasMoreSectionOpened[section.leader.id] = false;
    }
  }

  get smallestIndex() {
    return MaxColumnCount * 2 - 2;
  }

  subordinateVisibleRange(section: Section) {
    let maxIndex = section.subordinate.length - 1;
    const opened = this.hasMoreSectionOpened[section.leader.id];
    if (opened != null) {
      maxIndex = opened ? section.subordinate.length - 1 : this.smallestIndex;
    }
    return maxIndex;
  }

  toggleOpened(section: Section) {
    if (this.animating) return;
    this.animating = true;
    const index = this.sections.findIndex(item => item.leader.id === section.leader.id);
    const orgEl = this.sections[index].el;
    if (orgEl) {
      const cardEls = orgEl.querySelectorAll('.subordinates app-person-card.more') as NodeListOf<HTMLElement>;
      const opened = this.hasMoreSectionOpened[section.leader.id];
      const cards = opened ? gsap.utils.toArray(cardEls).reverse() : gsap.utils.toArray(cardEls);
      if (!opened) {
        gsap.set(cards, { display: 'block' });
      }
      gsap.to(cards, {
        display: opened ? 'none' : 'block',
        opacity: opened ? 0 : 1,
        scale: opened ? 0 : 1,
        duration: Duration,
        stagger: 0.1,
        ease: "back.out(1.7)",
        onComplete: () => {
          this.animating = false;
          this.hasMoreSectionOpened[section.leader.id] = !opened;
          this.cdr.markForCheck();
          setTimeout(() => {
            this.updateSectionNode();
            // console.log('sections', this.sections.map(item => item.el?.offsetHeight));
            this.updateScroll(this.orgWrapperEl.nativeElement);
          }, 0);
        }
      });
    }
  }

  private updateScroll(scroller: HTMLDivElement) {
    const overedSectionIndex = findLastIndex(this.sections, (item: Section) => item.top! + item.el!.offsetHeight / 2 <= scroller.scrollTop);
    const currentSection = this.sections[overedSectionIndex + 1];
    // console.log('end>>', this.activeSection, currentSection);
    if (currentSection && this.activeSection && currentSection?.leader.id !== this.activeSection.id) {
      this.selectPerson(currentSection.leader);
      this.cdr.markForCheck();
    }
  }


  jumpTo(index: number) {
    const target = this.sections[index];
    this.orgWrapperEl.nativeElement.scrollTo({
      top: target.top,
      behavior: 'smooth'
    });
  }

  jumpDirection(direction: -1 | 1) {
    this.selectPerson(this.sections[this.currentSecIndex + direction].leader);
  }

  get currentSecIndex() {
    return this.sections.findIndex(item => item.leader.id === this.activeSection?.id);
  }
}
